/**
 * @author JGY
 * @since 2023/12
 */
import {
  CadScene,
  CENTEREVENTS,
  CSLine,
  CSPoint,
  SceneSetup,
} from '@amcax/base';
import { ctx2 } from '@app-cad/module';
import {
  CatmullRomCurve3,
  Matrix4,
  Object3D,
  Scene,
  Vector2,
  Vector3,
} from 'three-legacy';
import { TransformControls } from 'three-legacy/examples/jsm/controls/TransformControls';
import { wsApi } from '@app-cad/server';
import { csEvent } from '@app-cad/common/events/EventCenter';
import { KEYBOARD_KEY } from '@app-cad/common/Constants';
import { MessageTip } from '@app-cad/common/utils/MessageTip';

enum State {
  NONE,
  CHOOSING,
  DRAGGING,
}

type MouseState = State.NONE | State.CHOOSING | State.DRAGGING;

/**
 * 曲线编辑器方法实例
 */
export class BsplineEditorSingleton {
  private static _instance: BsplineEditorSingleton;
  _dxid: string;
  _points: Array<Vector3>;
  state: MouseState;
  transformControl: TransformControls;
  trackballControl: any;
  sceneSetup: SceneSetup;
  assisGroup: Object3D;
  workScene: Scene;
  BsplineObj: CSLine;
  keyPointsMap: Map<string, Vector3>;
  targetPoint: CSPoint;
  keyPointsGroup: Object3D;
  onDownPosition: Vector2;
  onUpPosition: Vector2;
  controlOrigin: Vector3;

  constructor() {
    this.init();
  }

  public static getInstance(): BsplineEditorSingleton {
    if (!this._instance) {
      this._instance = new BsplineEditorSingleton();
    }
    return this._instance;
  }

  init() {
    this.controlOrigin = new Vector3();
    this.onDownPosition = new Vector2();
    this.onUpPosition = new Vector2();
    this.BsplineObj = null;
    this.keyPointsMap = new Map();
    this._dxid = null;
    this._points = [];
    this.state = State.NONE;
    this.targetPoint = null;
    this.keyPointsGroup = new Object3D();
    this.workScene = ctx2.viewer.sceneSetup.scene;
    const cadScene = ctx2.cadScene as CadScene;
    this.sceneSetup = ctx2.viewer.sceneSetup;
    this.assisGroup = cadScene.assisGroup;
    this.trackballControl = this.sceneSetup.trackballControls as any;
    // set transformControl
    this.transformControl = new TransformControls(
      this.sceneSetup.camera.clone(),
      this.sceneSetup.container,
    );
    this.transformControl.addEventListener('change', function () {
      // drag show/change
      ctx2.viewer.requestRender();
    });
    this.transformControl.addEventListener('dragging-changed', (event) => {
      // drag done

      this.sceneSetup.trackballControls.enabled = !event.value;

      if (event.value) {
        this.state = State.DRAGGING;
      } else {
        this.state = State.CHOOSING;
        this.updateKeyPoints();
        this.transformControl.detach();
      }
      ctx2.viewer.requestRender();
    });
    this.transformControl.addEventListener('objectChange', () => {
      // drag moving

      this.updateBspline();
    });
  }

  reset() {
    this._dxid = null;
    this._points = null;
    this.BsplineObj = null;
    if (this.keyPointsMap) this.keyPointsMap.clear();
    if (this.assisGroup) this.assisGroup.clear();
    if (this.keyPointsGroup) this.keyPointsGroup.clear();
  }

  start(dxid: string, points: Array<Vector3>) {
    this._dxid = dxid;
    this._points = points;
    this.drawKeyPoint();
    this.drawCurve();
    ctx2.viewer.requestRender();
    this.state = State.CHOOSING;

    this.workScene.add(this.transformControl);
    this.updateTransforControl();
    csEvent()
      .setState('bspline-edit')
      .on(CENTEREVENTS.POINTERDOWN, this.onPointerDown)
      .on(CENTEREVENTS.POINTERUP, this.onPointerUp)
      .on(CENTEREVENTS.POINTERMOVE, this.onPointerMove)
      .on(CENTEREVENTS.KEYUP, this.onKeyup)
      .addExit(this.exit);
    this.trackballControl.addEventListener(
      'change',
      this.updateTransforControl,
    );
    MessageTip.info('曲线编辑模式，可更改曲线关键点位置');
  }

  clear() {
    if (this.assisGroup) this.assisGroup.clear();
    if (this.keyPointsGroup) this.keyPointsGroup.clear();
  }

  exit = () => {
    this.reset();
    this.transformControl.detach();
    csEvent()
      .off(CENTEREVENTS.POINTERDOWN, this.onPointerDown)
      .off(CENTEREVENTS.POINTERUP, this.onPointerUp)
      .off(CENTEREVENTS.POINTERMOVE, this.onPointerMove)
      .off(CENTEREVENTS.KEYUP, this.onKeyup)
      .setDefault();
    this.trackballControl.removeEventListener(
      'change',
      this.updateTransforControl,
    );
    // ctx2.on(EVENTS.MODEL_ADDED, this.newCurveAdd)
    this.workScene.remove(this.transformControl);
    ctx2.viewer.requestRender();
    // ctx2.emit('ExitBspline')
    MessageTip.info('退出曲线编辑');
  };

  async finish() {
    if (this._points.length > 0) {
      const points = [];
      for (let i = 0, l = this._points.length; i < l; i++) {
        const point = [this._points[i].x, this._points[i].y, this._points[i].z];
        points.push(point);
      }
      await wsApi.setParams(this._dxid, { points: points });
      MessageTip.success('编辑完成');
    }
    this.exit();
  }

  drawKeyPoint() {
    for (let i = 0, l = this._points.length; i < l; i++) {
      const assistPointt = new CSPoint({
        vertex: this._points[i].clone(),
        pointType: 'endPoint',
      });
      this.keyPointsMap.set(assistPointt.uuid, this._points[i]);
      this.keyPointsGroup.add(assistPointt);
    }
    this.assisGroup.add(this.keyPointsGroup);
  }

  drawCurve() {
    if (!this.BsplineObj) {
      const curveX = new CatmullRomCurve3(this._points);
      const vertices = curveX.getPoints(50 * this._points.length);
      this.BsplineObj = new CSLine({
        vertexs: vertices,
        dashed: false,
      });
      this.assisGroup.add(this.BsplineObj);
    } else {
      const curveX = new CatmullRomCurve3(this._points);
      const vertices = curveX.getPoints(50 * this._points.length); // 细分曲线数量
      this.BsplineObj.update({ vertexs: vertices });
    }
  }

  updateKeyPoints() {
    const tx = this.targetPoint.position.x;
    const ty = this.targetPoint.position.y;
    const tz = this.targetPoint.position.z;
    const translationMatrix = new Matrix4();
    translationMatrix.makeTranslation(tx, ty, tz);
    this.targetPoint.geometry.applyMatrix4(translationMatrix);
    this.targetPoint.applyMatrix4(translationMatrix.invert());
  }

  updateBspline() {
    this._points = [];
    const worldPos = this.targetPoint.position
      .clone()
      .add(this.controlOrigin.clone());
    this.keyPointsMap.set(this.targetPoint.uuid, worldPos);
    for (const map of this.keyPointsMap) {
      this._points.push(map[1]);
    }
    this.drawCurve();
  }

  updateTransforControl = () => {
    this.transformControl.camera = this.sceneSetup.camera;
  };

  onKeyup = (e) => {
    if (e.key === KEYBOARD_KEY.ESC) {
      this.exit();
    } else if (e.key === KEYBOARD_KEY.ENTER) {
      this.finish();
    }
  };

  onPointerDown = (e) => {
    this.onDownPosition = new Vector2(e.mouse.x, e.mouse.y);
  };

  onPointerUp = (e) => {
    this.onUpPosition = new Vector2(e.mouse.x, e.mouse.y);
    if (e.button.onLeft) {
      if (this.onDownPosition.distanceTo(this.onUpPosition) === 0) {
        this.transformControl.detach();
        ctx2.viewer.requestRender();
      }
    } else if (e.button.onRight) {
      if (this.onDownPosition.distanceTo(this.onUpPosition) === 0) {
        this.finish();
      }
    }
  };

  onPointerMove = (e) => {
    if (
      !e.button.onLeft &&
      !e.button.onRight &&
      this.state === State.CHOOSING
    ) {
      const raycaster = this.sceneSetup.createRaycaster(e.mouse.x, e.mouse.y);
      const results = raycaster.intersectObjects(
        this.keyPointsGroup.children,
        true,
      );
      if (results && results.length > 0) {
        const targetPoint = results[0].object as CSPoint;
        this.controlOrigin = results[0].point;
        this.targetPoint = targetPoint;
        if (this.targetPoint !== this.transformControl.object) {
          this.transformControl.attach(this.targetPoint);
          this.transformControl.position.set(
            this.controlOrigin.x,
            this.controlOrigin.y,
            this.controlOrigin.z,
          );
        }
      }
    }
  };
}

/**
 * 曲线编辑器方法实例
 */
export const BsplineEditor = () => {
  return BsplineEditorSingleton.getInstance();
};
